package com.giteelou.antlr.ps.man;

import com.giteelou.antlr.ps.MyParser;
import com.giteelou.antlr.ps.MyParserBaseListener;
import com.giteelou.antlr.ps.man.symbol.Variable;
import com.giteelou.antlr.ps.man.type.Type;
import org.antlr.v4.runtime.Token;

import static com.giteelou.antlr.ps.man.type.PrimitiveType.*;

/**
 * 变量值类型推断，范围检查，类型匹配
 */
public class TypeResolver extends MyParserBaseListener {
    private final Semantics semantics;

    public TypeResolver(Semantics semantics) {
        this.semantics = semantics;
    }

    @Override
    public void exitVariableDeclaration(MyParser.VariableDeclarationContext ctx) {
        // 给变量值类型推断，匹配类型是否正确
        for (MyParser.VariableDeclaratorContext vCtx : ctx.variableDeclarators().variableDeclarator()) {
            Variable variable = (Variable) semantics.nodeSymbols.get(vCtx.variableDeclaratorId());
            Type declaredType = variable.getType();
            Type matchedType = semantics.nodeTypes.get(vCtx.variableInitializer());
            if (declaredType != null && matchedType != null) {
                MyParser.LiteralContext literal = vCtx.variableInitializer().literal();
                Token token = literal.getStart();
                if (!isTypeMatched(declaredType, matchedType)) {
                    semantics.errors.add("line " + token.getLine() + ":" + token.getStartIndex()
                            + " " + variable.getType() + " required but " + matchedType + " matched.");
                    return;
                }
                if (BYTE == declaredType) {
                    try {
                        int val;
                        if (CHAR == matchedType) {
                            val = literal.getText().charAt(1);
                        } else {
                            val = Integer.parseInt(literal.getText());
                        }
                        if (Byte.MIN_VALUE <= val && val <= Byte.MAX_VALUE) {
                            semantics.nodeTypes.put(literal, BYTE);
                        } else {
                            semantics.errors.add("line " + token.getLine() + ":" + token.getStartIndex()
                                    + " incompatible type.");
                        }
                    } catch (NumberFormatException e) {
                        semantics.errors.add("line " + token.getLine() + ":" + token.getStartIndex()
                                + " too large integer.");
                    }
                } else if (SHORT == declaredType) {
                    try {
                        int val;
                        if (CHAR == matchedType) {
                            val = literal.getText().charAt(1);
                        } else {
                            val = Integer.parseInt(literal.getText());
                        }
                        if (Short.MIN_VALUE <= val && val <= Short.MAX_VALUE) {
                            semantics.nodeTypes.put(literal, SHORT);
                        } else {
                            semantics.errors.add("line " + token.getLine() + ":" + token.getStartIndex()
                                    + " incompatible type.");
                        }
                    } catch (NumberFormatException e) {
                        semantics.errors.add("line " + token.getLine() + ":" + token.getStartIndex()
                                + " too large integer.");
                    }
                } else if (CHAR == declaredType
                        && INT == matchedType) {
                    try {
                        int val = Integer.parseInt(literal.getText());
                        if (Character.MIN_VALUE <= val && val <= Character.MAX_VALUE) {
                            semantics.nodeTypes.put(literal, CHAR);
                        } else {
                            semantics.errors.add("line " + token.getLine() + ":" + token.getStartIndex()
                                    + " incompatible type.");
                        }
                    } catch (NumberFormatException e) {
                        semantics.errors.add("line " + token.getLine() + ":" + token.getStartIndex()
                                + " too large integer.");
                    }
                } else if (INT == declaredType
                        && CHAR == matchedType) {
                    semantics.nodeTypes.put(literal, INT);
                } else if (FLOAT == declaredType) {
                    semantics.nodeTypes.put(literal, FLOAT);
                } else if (DOUBLE == declaredType) {
                    semantics.nodeTypes.put(literal, DOUBLE);
                }
            }
        }
    }

    @Override
    public void exitVariableDeclarator(MyParser.VariableDeclaratorContext ctx) {
        if (!(ctx.parent instanceof MyParser.VariableAssignmentContext)) {
            return;
        }
        MyParser.VariableDeclaratorIdContext idCtx = ctx.variableDeclaratorId();
        Variable variable = semantics.variables.get(idCtx.getText());
        if (variable == null) {
            Token token = idCtx.getStart();
            semantics.errors.add("line " + token.getLine() + ":" + token.getStartIndex()
                    + " variable " + idCtx.getText() + " not declared.");
            return;
        }
        Type declaredType = variable.getType();
        Type matchedType = semantics.nodeTypes.get(ctx.variableInitializer());
        if (declaredType != null && matchedType != null) {
            {
                MyParser.LiteralContext literal = ctx.variableInitializer().literal();
                Token token = literal.getStart();
                if (!isTypeMatched(declaredType, matchedType)) {
                    semantics.errors.add("line " + token.getLine() + ":" + token.getStartIndex()
                            + " " + variable.getType() + " required but " + matchedType + " matched.");
                    return;
                }
                if (BYTE == declaredType) {
                    try {
                        int val;
                        if (CHAR == matchedType) {
                            val = literal.getText().charAt(1);
                        } else {
                            val = Integer.parseInt(literal.getText());
                        }
                        if (Byte.MIN_VALUE <= val && val <= Byte.MAX_VALUE) {
                            semantics.nodeTypes.put(literal, BYTE);
                        } else {
                            semantics.errors.add("line " + token.getLine() + ":" + token.getStartIndex()
                                    + " incompatible type.");
                        }
                    } catch (NumberFormatException e) {
                        semantics.errors.add("line " + token.getLine() + ":" + token.getStartIndex()
                                + " too large integer.");
                    }
                } else if (SHORT == declaredType) {
                    try {
                        int val;
                        if (CHAR == matchedType) {
                            val = literal.getText().charAt(1);
                        } else {
                            val = Integer.parseInt(literal.getText());
                        }
                        if (Short.MIN_VALUE <= val && val <= Short.MAX_VALUE) {
                            semantics.nodeTypes.put(literal, SHORT);
                        } else {
                            semantics.errors.add("line " + token.getLine() + ":" + token.getStartIndex()
                                    + " incompatible type.");
                        }
                    } catch (NumberFormatException e) {
                        semantics.errors.add("line " + token.getLine() + ":" + token.getStartIndex()
                                + " too large integer.");
                    }
                } else if (CHAR == declaredType
                        && INT == matchedType) {
                    try {
                        int val = Integer.parseInt(literal.getText());
                        if (Character.MIN_VALUE <= val && val <= Character.MAX_VALUE) {
                            semantics.nodeTypes.put(literal, CHAR);
                        } else {
                            semantics.errors.add("line " + token.getLine() + ":" + token.getStartIndex()
                                    + " incompatible type.");
                        }
                    } catch (NumberFormatException e) {
                        semantics.errors.add("line " + token.getLine() + ":" + token.getStartIndex()
                                + " too large integer.");
                    }
                } else if (INT == declaredType
                        && CHAR == matchedType) {
                    semantics.nodeTypes.put(literal, INT);
                } else if (FLOAT == declaredType) {
                    semantics.nodeTypes.put(literal, FLOAT);
                } else if (DOUBLE == declaredType) {
                    semantics.nodeTypes.put(literal, DOUBLE);
                }
            }
        }
    }
}
